using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Resources;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Diagnostics;
using System.Data.SQLite;

namespace KeyboardImageViewer
{
    public class ViewerImage
    {
        private const int BgSamplingDepth = 2;

        private BitmapImage image;
        private int imageId;
        private int rotation;
		private Brush bgColor;
        private List<DuplicateImage> duplicates;
        private bool isAnimated;
        private bool fileExists = true;
        private bool corrupted;
        private ImageManager manager;

        public BitmapImage Image
        {
            get { return this.image; }
        }

        public int ImageId
        {
            get { return this.imageId; }
        }

        public int Rotation
        {
            get { return this.rotation; }
        }

        public Brush BgColor
		{
            get { return this.bgColor; }
		}

        public List<DuplicateImage> Duplicates
        {
            get { return this.duplicates; }
        }

		public bool IsAnimated
		{
			get { return this.isAnimated; } 
		}

        public bool FileExists
        {
            get { return this.fileExists; }
            set { this.fileExists = value; }
        }

        public bool Corrupted
        {
            get { return this.corrupted; }
            set { this.corrupted = value; }
        }

        public ViewerImage(int imageId, double currentAspectRatio, ImageManager imageManager, SQLiteConnection connection)
        {
            this.imageId = imageId;
            this.manager = imageManager;
            string imageName;

            SQLiteCommand getImageCommand = new SQLiteCommand("SELECT name FROM images WHERE id = " + imageId, connection);
            using (SQLiteDataReader reader = getImageCommand.ExecuteReader())
            {
                if (!reader.Read())
                {
                    return;
                }

                imageName = reader.GetString("name");
            }

            try
            {
                if (imageName.ToLowerInvariant().EndsWith(".gif") && File.Exists(imageName))
                {
                    System.Drawing.Bitmap gifBitmap = null;

                    try
                    {
                        gifBitmap = new System.Drawing.Bitmap(imageName);
                        System.Drawing.Imaging.FrameDimension dimension = new System.Drawing.Imaging.FrameDimension(gifBitmap.FrameDimensionsList[0]);
                        int frameCount = gifBitmap.GetFrameCount(dimension);

                        if (frameCount > 1)
                        {
                            this.isAnimated = true;
                        }
                    }
                    catch (ArgumentException)
                    {
                        // Thrown when file does not exist when creating Bitmap.
                        this.fileExists = false;
                        return;
                    }
                    finally
                    {
                        if (gifBitmap != null)
                        {
                            gifBitmap.Dispose();
                        }
                    }
                }

                if (!this.isAnimated)
                {
                    try
                    {
                        // Create a BitmapFrame object for reading metadata.
                        using (FileStream bitmapFrameStream = new FileStream(imageName, FileMode.Open, FileAccess.Read))
                        {
                            BitmapFrame frame = BitmapFrame.Create(bitmapFrameStream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);

                            // If we cannot cache the image, do not try to read metadata.
                            // This will lock the image for an indeterminate amount of time.

                            // Determine if the image needs to be rotated
                            if (frame != null && frame.Metadata != null)
                            {
                                BitmapMetadata metadata = (BitmapMetadata)frame.Metadata;
                                string rotationQuery = "/app1/ifd/{ushort=274}";
                                UInt16 orientationCode = 0;

                                if (metadata.ContainsQuery(rotationQuery))
                                {
                                    orientationCode = (UInt16)metadata.GetQuery(rotationQuery);
                                }

                                switch (orientationCode)
                                {
                                    case 1:
                                        break;
                                    case 3:
                                        this.rotation = 180;
                                        break;
                                    case 6:
                                        this.rotation = 90;
                                        break;
                                    case 8:
                                        this.rotation = 270;
                                        break;
                                    default:
                                        break;
                                }
                            }
                        }



                        // Load the image into memory
                        this.image = new BitmapImage();
                        this.image.BeginInit();
                        this.image.CacheOption = BitmapCacheOption.OnLoad;
                        this.image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;

                        //double maxWidth = SystemParameters.PrimaryScreenWidth;
                        //double maxHeight = SystemParameters.PrimaryScreenHeight;

                        //if (!this.manager.Config.OverlayInfobar)
                        //{
                        //    maxHeight -= 26;
                        //}

                        //double imageAspectRatio2 = ((double)imageWidth) / imageHeight;
                        //double maxAspectRatio = maxWidth / maxHeight;
                        //if (imageAspectRatio2 > maxAspectRatio && imageWidth > maxWidth)
                        //{
                        //    this.image.DecodePixelWidth = (int)maxWidth;
                        //}
                        //else if (imageHeight > maxHeight)
                        //{
                        //    this.image.DecodePixelHeight = (int)maxHeight;
                        //}

                        this.image.UriSource = new Uri(imageName);
                        this.image.EndInit();
                        this.image.Freeze();

                        // Find the background color for the image
                        double imageAspectRatio;
                        if (this.rotation == 0 || this.rotation == 180)
                        {
                            imageAspectRatio = ((double)image.PixelWidth) / image.PixelHeight;
                        }
                        else
                        {
                            imageAspectRatio = ((double)image.PixelHeight) / image.PixelWidth;
                        }

                        bool sideSpace = currentAspectRatio > imageAspectRatio;
                        bool fromSides;

                        // If the image needs to be rotated 90 or 270, we must summarize from the other orientation.
                        if (this.rotation == 0 || this.rotation == 180)
                        {
                            fromSides = sideSpace;
                        }
                        else
                        {
                            fromSides = !sideSpace;
                        }

                        this.bgColor = GetBufferColor(this.image, fromSides);

                        if (imageManager.Config.TrackDuplicates)
                        {
                            this.PopulateDuplicates(connection);
                        }
                    }
                    catch (NotSupportedException)
                    {
                        this.corrupted = true;
                        this.image = null;
                    }
                    catch (System.IO.FileFormatException)
                    {
                        this.corrupted = true;
                        this.image = null;
                    }
                    catch (System.Reflection.TargetInvocationException e)
                    {
                        MessageBox.Show("imageName: " + imageName + Environment.NewLine +
                            e.ToString());
                        this.corrupted = true;
                        this.image = null;
                    }
                }
            }
            catch (FileNotFoundException)
            {
                // If the file isn't here anymore, just return an empty ViewerImage.
                this.fileExists = false;
            }
        }

        public void PopulateDuplicates(SQLiteConnection connection)
        {
            // Find image duplicates
            this.duplicates = new List<DuplicateImage>();

            List<int> exactDuplicates = DuplicateUtils.FindExactDuplicates(connection, this.imageId);

            foreach (int duplicateImageId in exactDuplicates)
            {
                this.duplicates.Add(new DuplicateImage { ImageId = duplicateImageId, ExactDuplicate = true });
            }

            if (this.manager.Config.TrackImageDuplicates)
            {
                List<int> imageDuplicates = DuplicateUtils.FindDuplicatesForImage(this.imageId, connection);

                // Add all image duplicates that aren't already exact duplicates
                foreach (int duplicateImageId in imageDuplicates)
                {
                    bool match = false;
                    foreach (int previousDuplicateId in exactDuplicates)
                    {
                        if (duplicateImageId == previousDuplicateId)
                        {
                            match = true;
                            break;
                        }
                    }

                    if (!match)
                    {
                        this.duplicates.Add(new DuplicateImage { ImageId = duplicateImageId, ExactDuplicate = false });
                    }
                }

            }
        }

        private Brush GetBufferColor(BitmapImage image, bool fromSides)
		{
            return GetBufferColorSub(image, BgSamplingDepth, fromSides);
		}

        private static Brush GetBufferColorSub(BitmapImage image, int bgSamplingDepth, bool fromSides)
		{
			int totalRed = 0;
			int totalGreen = 0;
			int totalBlue = 0;

			int totalPixels = 0;

			Brush defaultColor = Brushes.Black;

            PixelFormat pixelFormat = image.Format;

            if (pixelFormat.BitsPerPixel % 8 != 0)
            {
                return defaultColor;
            }

            int bytesPerPixel = pixelFormat.BitsPerPixel / 8;

			if (fromSides)
			{
				if (image.Width < bgSamplingDepth)
				{
					return defaultColor;
				}

                Array buffer = new byte[bgSamplingDepth * image.PixelHeight * bytesPerPixel];
                image.CopyPixels(new Int32Rect(0, 0, bgSamplingDepth, image.PixelHeight), buffer, bgSamplingDepth * bytesPerPixel, 0);
                GetAverageColor(buffer, ref totalRed, ref totalGreen, ref totalBlue, image);

                image.CopyPixels(new Int32Rect(image.PixelWidth - bgSamplingDepth, 0, bgSamplingDepth, image.PixelHeight), buffer, bgSamplingDepth * bytesPerPixel, 0);
                GetAverageColor(buffer, ref totalRed, ref totalGreen, ref totalBlue, image);

                totalPixels = bgSamplingDepth * image.PixelHeight * 2;
			}
			else
			{
				if (image.Height < bgSamplingDepth)
				{
					return defaultColor;
				}

                Array buffer = new byte[image.PixelWidth * bgSamplingDepth * bytesPerPixel];
                image.CopyPixels(new Int32Rect(0, 0, image.PixelWidth, bgSamplingDepth), buffer, image.PixelWidth * bytesPerPixel, 0);
                GetAverageColor(buffer, ref totalRed, ref totalGreen, ref totalBlue, image);

                image.CopyPixels(new Int32Rect(0, image.PixelHeight - bgSamplingDepth, image.PixelWidth, bgSamplingDepth), buffer, image.PixelWidth * bytesPerPixel, 0);
                GetAverageColor(buffer, ref totalRed, ref totalGreen, ref totalBlue, image);

                totalPixels = image.PixelWidth * bgSamplingDepth * 2;
			}

            SolidColorBrush finalColor = new SolidColorBrush(Color.FromRgb((byte)(totalRed / totalPixels), (byte)(totalGreen / totalPixels), (byte)(totalBlue / totalPixels)));
            finalColor.Freeze();

            return finalColor;
		}

        private static void GetAverageColor(Array data, ref int totalRed, ref int totalGreen, ref int totalBlue, BitmapImage image)
		{
            PixelFormat pixelFormat = image.Format;

            byte[] bytes = (byte[]) data;

            if (pixelFormat == PixelFormats.Bgr32 || pixelFormat == PixelFormats.Bgra32)
            {
                for (int i = 0; i < bytes.Length; i += 4)
                {
                    totalBlue += bytes[i];
                    totalGreen += bytes[i + 1];
                    totalRed += bytes[i + 2];
                }
            }
            else if (pixelFormat == PixelFormats.Gray8)
            {
                for (int i = 0; i < bytes.Length; i++)
                {
                    totalBlue += bytes[i];
                    totalGreen += bytes[i];
                    totalRed += bytes[i];
                }
            }
            else if (pixelFormat == PixelFormats.Indexed8)
            {
                for (int i = 0; i < bytes.Length; i++)
                {
                    int colorIndex = (int)bytes[i];

                    Color pixelColor = image.Palette.Colors[colorIndex];

                    totalBlue += pixelColor.B;
                    totalGreen += pixelColor.G;
                    totalRed += pixelColor.R;
                }
            }
		}
	}
}
